-- Copyright (c) 2015,2016 Lucky Byte, Inc.
--
-- Summary : POS 009.1 内部交易处理(消费代付)
--
-- Author  : 吴小虎 <xiaohu@lucky-byte.com>
-- Since   : 2016.7.11
--
-- History
--  * 2016.7.11 : 创建此文件
--  * 2016.8.30 : 隔日不能代付
--

-- 通过消费代付终端查询消费代付配置
--
local _tz0_query_p1 = function(tz0_meta)
    local _table = jdbc.select([[
        select * from pay_p1 where tz0_termid = ?
    ]], reqt.term.get('uuid'))

    if _table.count == 0 then
        notify.error('消费代付终端[%s][%s]未配置消费代付产品，请检查.',
            reqt.term.get('termid'), reqt.term.get('uuid'))
        return '03'
    end
    if _table.count > 1 then
        logger.error('消费代付配置错误，代付终端[%s][%s]存在于多条配置记录中.',
            reqt.term.get('termid'), reqt.term.get('uuid'))
        return '01'
    end
    local _p1_record = _table.get_record(0)
    tz0_meta.p1_record = _p1_record

    logger.trace('查询到此代付终端的消费代付配置[%s].', _p1_record.get('uuid'))

    local _requires = {
        acct_no     = '代付收款账号',
        pay_chnl    = '代付渠道',
        pay_merid   = '代付渠道商户',
    }
    for _key, _name in pairs(_requires) do
        local _v = _p1_record.get(_key)
        if not _v or string.trimlen(tostring(_v)) == 0 then
            notify.error('消费代付配置[%s]错误，[%s]为空，请检查.',
                _p1_record.get('uuid'), _name)
            return resp.respkt('01')
        end
    end
    -- 身份认证要素(证件号码或姓名)必须至少提供一项
    if  string.trimlen(_p1_record.get('acct_name')) == 0 and
        string.trimlen(_p1_record.get('acct_cred_no')) == 0 then
        notify.error(
            '消费代付配置[%s]错误，收款账户身份认证信息必须至少提供姓名或证件号码中的一项.',
            _p1_record.get('uuid'))
        return resp.respkt('01')
    end
    return '00'
end


-- 解析消费代付报文 48 域
--
local _tz0_split_f48 = function(tz0_meta)
    local _priv = reqt.packet.get('48')
    if not _priv then
        logger.error('终端上送代付报文缺少[48]域，不能查询原消费交易信息.')
        return '10'
    end
    if string.len(_priv) < 166 then
        logger.error('终端上送代付报文[48]域数据长度[%s]错误.', string.len(_priv))
        return '10'
    end

    tz0_meta.f48 = {
        usage    = string.sub(_priv, 1, 2),      -- 用法
        _        = string.sub(_priv, 3, 4),      -- 用户号码类型
        _        = string.sub(_priv, 5, 44),     -- 用户号码
        _        = string.sub(_priv, 45, 48),    -- 用户号码地区编码
        _        = string.sub(_priv, 49, 52),    -- 用户号码附加地区编码
        _        = string.sub(_priv, 53, 58),    -- 缴费月份
        _        = string.sub(_priv, 59, 75),    -- 支付区间
        data_len = string.sub(_priv, 76, 78),    -- 附加子域长度
        merno    = string.sub(_priv, 79, 93),    -- 原商户编号
        termid   = string.sub(_priv, 94, 101),   -- 原终端编号
        batchnum = string.sub(_priv, 102, 107),  -- 原始交易批次号
        tracenum = string.sub(_priv, 108, 113),  -- 原始交易POS流水号
        authcode = string.sub(_priv, 114, 119),  -- 原始交易授权号
        retrefno = string.sub(_priv, 120, 131),  -- 检索参考号
        trtime   = string.sub(_priv, 132, 135),  -- 原始交易日期
        pri_acct = string.sub(_priv, 136, 154),  -- 原交易主账号
        tramt    = string.sub(_priv, 155, 166),  -- 原交易金额
    }
    return '00'
end


-- 通过代付报文中 48 域查询原消费流水记录
--
local _tz0_query_t20 = function(tz0_meta)
    local _table = jdbc.select([[
        select * from pay_trlist
        where
            sys_time::date = CURRENT_DATE and
            status = 0 and chnl = 1 and incoming = true and
            json_extract_path_text(i_pkt, '4')  = ? and
            json_extract_path_text(i_pkt, '11') = ? and
            json_extract_path_text(i_pkt, '41') = ? and
            json_extract_path_text(i_pkt, '42') = ? and
            json_extract_path_text(o_pkt, '37') = ? and
            json_extract_path_text(o_pkt, '39') = '00'
    ]], tz0_meta.f48.tramt,  tz0_meta.f48.tracenum,
        tz0_meta.f48.termid, tz0_meta.f48.merno, tz0_meta.f48.retrefno)

    if _table.count == 0 then
        notify.error('通过代付交易报文信息未查询到对应的消费交易，不允许代付.')
        return '14'
    end
    assert(_table.count == 1, '通过消费代付报文查询到多笔原消费交易.')

    tz0_meta.t20_record = _table.get_record(0)
    logger.trace('查询到此消费代付交易对应的原消费交易为[%s].',
        tz0_meta.t20_record.get('uuid'))
    return '00'
end


-- 通过消费流水查询消费代付订单
--
local _tz0_query_p1_order = function(tz0_meta)
    local _table = jdbc.select([[
        select * from pay_p1_order where p1_uuid = ? and t20_uuid = ?
    ]], tz0_meta.p1_record.get('uuid'), tz0_meta.t20_record.get('uuid'))

    if _table.count == 0 then
        notify.error('未找到原消费交易创建的消费代付订单，不允许代付.')
        return '05'
    end
    assert(_table.count == 1, '原消费交易创建了多笔消费代付订单?')

    tz0_meta.p1_order = _table.get_record(0)
    logger.trace('查询到此消费代付交易对应的代付订单为[%s].',
        tz0_meta.p1_order.get('uuid'))

    -- 立即更新已处理标志，避免后续的重复代付
    local _table = jdbc.select([[
        update pay_p1_order as o1 set received = true from (
            select received from pay_p1_order where uuid = ? for update
        ) as o2
        where uuid = ?
        returning o1.received as new_received, o2.received as old_received
    ]], tz0_meta.p1_order.get('uuid'), tz0_meta.p1_order.get('uuid'))

    -- 检查订单已处理标志，一笔订单不允许多次代付
    local _record = _table.get_record(0)
    if _record.get('old_received') then
        notify.info('消费代付订单[%s]已被受理过，忽略重复(也可能是延迟)的代付交易.',
            tz0_meta.p1_order.get('uuid'))
        return '13'
    end
    if not _record.get('new_received') then
        notify.error('更新消费代付订单[%s]已受理标志失败，代付失败.',
            tz0_meta.p1_order.get('uuid'))
        return '01'
    end
    return '00'
end


-- 通过全渠道发送代付交易
--
local _tz0_pay_by_3 = function(tz0_meta, pay_amt)
    local _071_packet = lib071.new_packet()

    -- 代付报文常数信息
    _071_packet.set('txnType', '12')
    _071_packet.set('txnSubType', '00')
    _071_packet.set('bizType', '000401')
    _071_packet.set('txnTime', os.date('%Y%m%d%H%M%S'))
    _071_packet.set('accessType', '0')
    _071_packet.set('txnAmt', round(pay_amt * 100, 0))

    -- 填充报文中商户相关的信息
    _071_packet.set('merId', tz0_meta.pay_merch.get('merno'))
    _071_packet.set('orderId', core.order_id())

    -- 收款账号
    _071_packet.set('accType', '01')
    _071_packet.set('accNo', tz0_meta.p1_record.get('acct_no'))
    _071_packet.set('termId', tz0_meta.f48.termid)

    -- 构建身份验证信息字符串
    local  _custinfo = core.acp_aux.build_custinfo({
        acct_name = tz0_meta.p1_record.get('acct_name'),
        cred_no   = tz0_meta.p1_record.get('acct_cred_no'),
        cred_type = tz0_meta.p1_record.get('acct_cred_type')
    })
    _071_packet.set('customerInfo', _custinfo)

    -- 发送报文
    local _fowd = lib071.forward({
        uuid = reqt.uuid, packet = _071_packet, indirect = true,
        merch = tz0_meta.pay_merch
    })
    if not _fowd or not _fowd.respkt then
        return '11'
    end
    if not lib071.respkt_ok(_fowd.respkt) then
        return _fowd.rescode and _fowd.rescode or '11'
    end
    logger.trace('银联 ACP 代付交易响应成功，需进一步等待后台通知确定此交易最终状态...')
    return '00'
end


-- 通过配置执行代付
--
local _tz0_pay = function(tz0_meta)
    -- 手续费在登记订单时已计算
    local _p1_fee = tz0_meta.p1_order.get('p1_fee')
    if _p1_fee < 0 then
        logger.error('手续费[%s]无效，停止代付.', _p1_fee)
        return '01'
    end
    -- 实际代付金额 = 原消费金额 - 手续费
    local _tramt = tonumber(tz0_meta.f48.tramt) / 100
    local _pay_amt = _tramt - _p1_fee

    logger.info('原消费金额[%s]元 - 手续费[%s]元 = 实际代付金额[%s]元...',
        _tramt, _p1_fee, _pay_amt)

    -- 代付金额必须大于 0
    if _pay_amt <= 0.0 then
        notify.error('实际代付金额[%s]小于或等于[0.0]，不能代付.', _pay_amt)
        return '05'
    end

    local _pay_chnl  = tz0_meta.p1_record.get('pay_chnl')
    local _pay_merid = tz0_meta.p1_record.get('pay_merid')

    -- 查询代付商户信息
    local _table = jdbc.select([[
        select * from pay_merch where chnl = ? and uuid = ?
    ]], _pay_chnl, _pay_merid)

    -- 如果没有查询到代付商户
    if _table.count == 0 then
        notify.error('未能查询到代付商户[%s]的信息，不能代付.', _pay_merid)
        return '01'
    end
    tz0_meta.pay_merch = _table.get_record(0)

    -- 通过全渠道发送代付交易
    local _pay_ret
    if _pay_chnl == 3 then
        _pay_ret = _tz0_pay_by_3(tz0_meta, _pay_amt)
    else
        notify.error('平台不支持通过代付渠道[%s]进行代付，请检查配置.', _pay_chnl)
        return '01'
    end

    -- 更新订单代付交易流水
    local _status = _pay_ret == '00' and 2 or 3
    local _notes  = _pay_ret == '00' and '代付交易发送成功，尚未确认' or '代付交易失败，尚未确认'
    jdbc.update([[
        update pay_p1_order set
            status = ?, t40_uuid = ?, notes = ?
        where uuid = ?
    ]], _status, reqt.uuid, _notes, tz0_meta.p1_order.get('uuid'))
    return _pay_ret
end


-- 构造 POS 009.1 消费代付成功响应报文
--
local _tz0_build_respkt = function()
    local _respkt = resp.respkt('00')

    _respkt.set('3',  reqt.packet.get('3'))
    _respkt.set('4',  reqt.packet.get('4'))
    _respkt.set('11', reqt.packet.get('11'))
    _respkt.set('12', os.date('%H%M%S'))
    _respkt.set('13', os.date('%m%d'))
    _respkt.set('15', os.date('%m%d'))
    _respkt.set('25', reqt.packet.get('25'))
    _respkt.set('37', os.date('%y%m%d%H%M%S'))
    _respkt.set('41', reqt.packet.get('41'))
    _respkt.set('42', reqt.packet.get('42'))
    _respkt.set('49', reqt.packet.get('49'))

    -- 添加报文 MAC 域
    -- 这里不再检查 工作密钥 等信息，因为在交易开始时已经检查过
    local _keys = core.json.decode(reqt.term.get('keys'))
    lib091.set_mac(_respkt, reqt.term.get('tmk'),
        _keys.mak, reqt.term.get('macalgo'))
    return _respkt
end


-- 消费代付交易
--
-- 处理流程：
--  1. 通过代付终端查询消费代付配置，得到唯一记录，如果查询不到，则失败(无效的终端);
--  2. 通过代付报文查询原始消费交易，得到唯一记录，如果查询不到则失败(有代付、无消费)；
--  3. 通过原消费流水和订单配置查询消费代付订单，得到唯一记录，如果查询不到则失败(订单无效或终端绑定关系无效)；
--  4. 自此，所有关系验证完成，开始执行代付；
--
local _inner_proc_tz0 = function()
    logger.debug('开始处理 POS 009.1 消费代付交易...')

    -- 创建一个名字空间，用于存储中间数据
    local _tz0_meta = {}

    -- 1. 查询消费代付配置
    local _rescode = _tz0_query_p1(_tz0_meta)
    if _rescode ~= '00' then
        return resp.respkt(_rescode)
    end

    -- 解析代付报文 48 域
    local _rescode = _tz0_split_f48(_tz0_meta)
    if _rescode ~= '00' then
        return resp.respkt(_rescode)
    end

    -- 2. 通过代付报文 48 域查询原消费流水
    local _rescode = _tz0_query_t20(_tz0_meta)
    if _rescode ~= '00' then
        return resp.respkt(_rescode)
    end

    -- 3. 检查原消费流水是否创建了消费代付订单
    local _rescode = _tz0_query_p1_order(_tz0_meta)
    if _rescode ~= '00' then
        return resp.respkt(_rescode)
    end

    -- 4. 所有条件都满足，开始执行代付
    local _rescode = _tz0_pay(_tz0_meta)
    if _rescode ~= '00' then
        return resp.respkt(_rescode)
    end
    return _tz0_build_respkt()
end


return _inner_proc_tz0
